package core.modeling.metamodel;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.jdom.Attribute;
import org.jdom.Element;

import core.ConstantesAnaliseSemantica;
import core.generator.Codigo;
import core.generator.ConstantesGeracaoDeCodigo;
import core.generator.GeradorDeCodigoUtils;
import core.modeling.GerenciadorEntidades;

public class Metodo extends Elemento {
	
	private boolean estatico;
	private boolean abstrato;
	private boolean metodoFinal;
	private boolean recebeFiltro;
	private String traducao;

	private String nome;
	private VisibilidadeKind visibilidade;
	private Codigo codigo;
	private Classe tipoRetorno;
	private List<Parametro> parametros;
	private List<Constraint> constraints;

	public Metodo(String nomeOp, Classe tipoRetorno, Classe... tiposParametros) {
		this(false, nomeOp, tipoRetorno, tiposParametros);
	}
	
	public Metodo(boolean recebeFiltro, String nomeOp, Classe tipoRetorno, Classe... tiposParametros) {
		this(nomeOp);
		this.tipoRetorno = tipoRetorno;
		this.recebeFiltro = recebeFiltro;

		for (Classe classe : tiposParametros) {
			addParametro(new Parametro("", classe));
		}
	}

	public Metodo(String nome) {
		this.parametros = new ArrayList<Parametro>();
		this.constraints = new ArrayList<Constraint>();
		this.nome = nome;
		this.visibilidade = VisibilidadeKind.PUBLIC;
		this.traducao = null;
	}
	
	public boolean hasTraducao(){
		return traducao != null;
	}
	
	public String getTraducao(){
		return traducao;
	}
	
	public void setTraducao(String t){
		this.traducao = t;
	}

	@SuppressWarnings("unchecked")
	public Metodo(Element elemento, List<Classe> classes)
			throws ModelingException {
		super(elemento);

		Attribute attrName = elemento.getAttribute("name");
		Attribute attrVis = elemento.getAttribute("visibility");

		this.constraints = new ArrayList<Constraint>();
		this.parametros = new ArrayList<Parametro>();
		this.setNome(attrName.getValue());
		this.setVisibilidade(attrVis.getValue());
		this.read(elemento.getChildren(), classes);
	}
	
	public void addConstraint(Constraint c){
		constraints.add(c);
	}

	public String getNome() {
		return nome;
	}

	public void setVisibilidade(String visibilidade) {
		this.visibilidade = VisibilidadeKind.getByDescricao(visibilidade);
	}

	public VisibilidadeKind getVisibilidade() {
		return visibilidade;
	}

	public void setTipoRetorno(Classe tipoRetorno) {
		this.tipoRetorno = tipoRetorno;
	}

	public Classe getTipoRetorno() {
		return tipoRetorno;
	}

	public void setMetodoFinal(boolean metodoFinal) {
		this.metodoFinal = metodoFinal;
	}

	public boolean isMetodoFinal() {
		return metodoFinal;
	}

	public void setAbstrato(boolean abstrato) {
		this.abstrato = abstrato;
	}

	public boolean isAbstrato() {
		return abstrato;
	}

	public void setEstatico(boolean estatico) {
		this.estatico = estatico;
	}

	public boolean isEstatico() {
		return estatico;
	}

	public void setNome(String nome) {
		this.nome = nome;
	}

	public List<Parametro> getParametros() {
		return parametros;
	}

	public int getQuantidadeParametros() {
		return parametros.size();
	}

	public void addParametro(Parametro p) {
		parametros.add(p);
	}

	public void setRecebeFiltro(boolean recebeFiltro) {
		this.recebeFiltro = recebeFiltro;
	}

	public boolean isRecebeFiltro() {
		return recebeFiltro;
	}

	public void setCodigo(Codigo codigo) {
		this.codigo = codigo;
	}

	public Codigo getCodigo() {
		return codigo;
	}

	public Codigo generateJavaCode() {
		Codigo codigo = new Codigo();
		Collections.sort(constraints, new Comparator<Constraint>() {
			public int compare(Constraint o1, Constraint o2) {
				return o2.getStereotype().compareTo(o1.getStereotype());
			}
		});
		for (Constraint c : constraints)
			codigo.addLine(1, c.generateJMLCode().toString());
		if (visibilidade != null)
			codigo.add(1, visibilidade.toJava());
		if (estatico)
			codigo.add(" static");
		codigo.add(" " + GeradorDeCodigoUtils.oclLineToJML(ConstantesGeracaoDeCodigo.JML_PURE));
		codigo.addLine(" " + GeradorDeCodigoUtils.oclTypeToJava(tipoRetorno) + " " + nome
				+ getParameterListString() + " {");
		codigo.addLine(2, GeradorDeCodigoUtils.defaultReturnType(tipoRetorno));
		codigo.addLine(1, "}");
		return codigo;
	}

	private String getParameterListString() {
		StringBuilder out = new StringBuilder();
		out.append("(");
		String separador = "";
		for (Parametro p : parametros) {
			out.append(separador + GeradorDeCodigoUtils.oclTypeToJava(p.getTipo()) + " " + p.getNome());
			separador = ", ";
		}
		out.append(")");
		return out.toString().trim();
	}

	private void read(List<Element> children, List<Classe> classes)
			throws ModelingException {
		if (children != null && !children.isEmpty()) {
			for (Element child : children) {
				Atributo atributo = new Atributo(child, classes);
				Attribute attribute = child.getAttribute("direction");

				if (attribute != null) {
					if (attribute.getValue().equals("return")) {
						setTipoRetorno(atributo.getTipo());
					} else {
						parametros.add(new Parametro(atributo.getNome(),
								atributo.getTipo()));
					}
				} else {
					parametros.add(new Parametro(atributo.getNome(), atributo
							.getTipo()));
				}
			}
		}

		if (tipoRetorno == null) {
			setTipoRetorno(GerenciadorEntidades.getInstance().getClasse(
					ConstantesAnaliseSemantica.VOID));
		}
	}
}
